package com.sojson.util.encrypt.bothway.imparity.sm2.impl.sign;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.sojson.util.StringUtil;
import com.sojson.util.Util;
import com.sojson.util.encrypt.bothway.imparity.sm2.ISm2SignUtil;
import com.sojson.util.encrypt.bothway.imparity.sm2.Sm2Util;

import lombok.extern.slf4j.Slf4j;

/**
 * Sms2双向非对称加解密工具类
 * 
 * @author liu
 * @date 2020-09-04
 */
@Slf4j
public class Sm2Sign2UtilImpl implements ISm2SignUtil {

    private static Sm2Sign2UtilImpl instances;

    private final BouncyCastleProvider BC = new BouncyCastleProvider();
    private KeyFactory KEY_FACT = null;

    {
        try {
            KEY_FACT = KeyFactory.getInstance("EC", BC);
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
    * 签名
    * @param data 数据
    * @return 签名
    */
    @Override
    public String sign(String data, String privateKey) throws Exception {
        return signToStr(data.getBytes(), privateKey);
    }

    /**
    * 签名
    * @param data 数据
    * @return 签名
    */
    @Override
    public byte[] signToByte(String data, String privateKey) throws Exception {
        return sign(data.getBytes(), privateKey);
    }

    /**
    * 签名
    * @param data 数据
    * @return 签名
    */
    @Override
    public String signToStr(byte[] data, String privateKey) throws Exception {
        return Sm2Util.encryptContentToStr(signInit(data, privateKey));
    }

    /**
    * 签名
    * @param data 数据
    * @return 签名
    */
    @Override
    public byte[] sign(byte[] data, String privateKey) throws Exception {
        return signToStr(data, privateKey).getBytes();
    }

    /**
    * 签名
    * @param data 数据
    * @return 签名
    */
    private byte[] signInit(byte[] data, String privateKey) throws Exception {
        byte[] privateKeyByte = Util.hexToByte(privateKey);
        PrivateKey generatePrivate = KEY_FACT.generatePrivate(new PKCS8EncodedKeySpec(privateKeyByte));
        // 写入签名原文到算法中
        Signature signature =
            Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), new BouncyCastleProvider());
        signature.initSign(generatePrivate);
        signature.update(data);
        return signature.sign();
    }

    /**
     * 用公钥检验数字签名的合法性
     * @param data      数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 是否验证通过
     */
    @Override
    public boolean verify(String data, String sign, String publicKey) throws Exception {
        return verifyInit(data.getBytes(), Sm2Util.decryptContentToByte(sign), publicKey);
    }

    /**
     * 用公钥检验数字签名的合法性
     * @param data      数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 是否验证通过
     */
    /**
     * 用公钥检验数字签名的合法性
     * 
     * @param data
     * @param sign
     * @param publicKey
     * @return
     * @throws Exception
     */
    @Override
    public boolean verify(byte[] data, byte[] sign, String publicKey) throws Exception {
        return verify(new String(data), new String(sign), publicKey);
    }

    /**
     * 用公钥检验数字签名的合法性
     * @param data      数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 是否验证通过
     */
    private boolean verifyInit(byte[] data, byte[] sign, String publicKey) throws Exception {
        byte[] publicKeyByte = Util.hexToByte(publicKey);
        PublicKey generatePublic = KEY_FACT.generatePublic(new X509EncodedKeySpec(publicKeyByte));
        // 写入签名原文到算法中
        Signature signature =
            Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), new BouncyCastleProvider());
        signature.initVerify(generatePublic);
        // 写入待验签的签名原文到算法中
        signature.update(data);
        // 验签
        return signature.verify(sign);
    }

    /**
     * 判断一个对象是否是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isBlankObject(Object obj) {
        return StringUtil.isBlankObject(obj);
    }

    public static Sm2Sign2UtilImpl getInstances() {
        if (isBlankObject(instances)) {
            instances = new Sm2Sign2UtilImpl();
        }
        return instances;
    }

}